/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package rs232_test;

//~--- JDK imports ------------------------------------------------------------
import java.io.*;

import java.util.*;

/**
 *
 * @author sf
 */
public class Rs485 {

    /** Field description */
    private static final byte PKT_START = 0x02;
    private byte[] cData;
    private byte cLen;
    private CommProt commProt;
    private byte command;
    /** Field description */
    public byte[] data;
    /** Field description */
    public int len;
    private byte rs485RxChar;
    public RxCommPack rxCommPack;
    private TxCommPack txCommPack;


    /*
     * @Override
     * public int read() throws IOException {
     * throw new UnsupportedOperationException("Not supported yet.");
     * }
     */
    /**
     * Enum description
     *
     */
    public enum errState {

        BAD_LENGTH, BAD_CRC, OK_CRC
    }

    /**
     * Enum description
     *
     */
    public enum rxState {

        WAIT_START, WAIT_ADDR_HIGH, WAIT_ADDR_LOW, PKT_CMD, WAIT_LEN, WAIT_DATA,
        WAIT_CRC_HIGH, WAIT_CRC_LOW, COMPLETE, VALID, INVALID
    }

    /**
     * Constructs ...
     *
     */
    Rs485(char Addres) {
        rxCommPack = new RxCommPack();
        rxCommPack.c485Buf = new byte[64];
        txCommPack = new TxCommPack();
        commProt = new CommProt();
        commProt.cOurAddr = Addres;
        commProt.cError = errState.OK_CRC;
    }

    public void setCommand(byte command) {
        this.command = command;
    }

    public byte getCommand() {
        return command;
    }

    public byte[] getData() {
        return data;
    }

    public int getLen() {
        return len;
    }

    public void setrs485RxChar(byte rs485RxChar) {
        this.rs485RxChar = rs485RxChar;
        System.out.println(rs485RxChar);
        rs485Decode(rs485RxChar);
        rs485Process(rs485RxChar);
    }

    rxState rs485Decode(byte rs485RxChar) {

        /*
         * Set<rxState> stateSet = EnumSet.allOf(rxState.class);
         * Iterator<rxState> stateIterator = stateSet.iterator();
         */
        switch (commProt.cRS485State) {
            case WAIT_START:
                db(rs485RxChar);
                db(commProt.cRS485State);
                rxCommPack.cStart = rs485RxChar;

//          Check for the start of packet byte

                if (rxCommPack.cStart == PKT_START) {
                    db(commProt.cRS485State);
                    commProt.cRS485State = nextRxState(commProt.cRS485State);
                }

                break;

            case WAIT_ADDR_LOW:
                db(commProt.cRS485State);

                char tmp = (char) (rxCommPack.cNetAddr & 0x0f);
tmp = (char) (rs485RxChar | tmp);
                rxCommPack.cNetAddr = (char) (rs485RxChar | (rxCommPack.cNetAddr ));
                commProt.cRS485State = nextRxState(commProt.cRS485State);

                break;

            case WAIT_ADDR_HIGH:
                db(commProt.cRS485State);
                char tmp1;
                char tmp2;
                tmp1 = (char) (rs485RxChar << 8);
                tmp2 = (char) (rxCommPack.cNetAddr & 0xf0);
                char chk = (char) (tmp1 | tmp2);
                rxCommPack.cNetAddr = (char) ((rs485RxChar << 8) | (rxCommPack.cNetAddr & 0xf0));
                commProt.cRS485State = nextRxState(commProt.cRS485State);

                break;

            case WAIT_LEN:
                db(commProt.cRS485State);

                rxCommPack.cLenExpected = rs485RxChar;

                if (rxCommPack.cLenExpected > rxCommPack.c485Buf.length) {
                    commProt.cRS485State = rxState.INVALID;

                    int a;

                    commProt.cError = errState.BAD_LENGTH;
                } else {
                    commProt.cBufPtr = 0;
                    commProt.cRS485State = nextRxState(commProt.cRS485State);
                }

                break;

            case PKT_CMD:
                db(commProt.cRS485State);

                rxCommPack.cCommand = rs485RxChar;

                // FIXME PacketHasPayload
                if (true) {
                    commProt.cRS485State = nextRxState(commProt.cRS485State);
                //commProt.cRS485State = rxState.WAIT_DATA;
                } else {
                    commProt.cRS485State = rxState.WAIT_CRC_HIGH;
                }

                break;

            case WAIT_DATA:
                db(commProt.cRS485State);

                rxCommPack.c485Buf[(int) commProt.cBufPtr] = rs485RxChar;
                commProt.cBufPtr++;
                /*commProt.cRS485State                       =
                nextRxState(commProt.cRS485State);*/

                // If last byte of data received
                if (commProt.cBufPtr == rxCommPack.cLenExpected) {

                    // next byet is the CRC high byte
                    commProt.cRS485State = nextRxState(commProt.cRS485State);
                }

                break;

            case WAIT_CRC_HIGH:
                db(commProt.cRS485State);

                rxCommPack.cRxCrc = rs485RxChar;
                commProt.cRS485State = rxState.WAIT_CRC_LOW;

                break;

            case WAIT_CRC_LOW:
                db(commProt.cRS485State);

                rxCommPack.cRxCrc = rs485RxChar;
                commProt.cRS485State = rxState.COMPLETE;

                break;

            case COMPLETE:
                db(commProt.cRS485State);

                break;    // Idle state

            case VALID:
                db(commProt.cRS485State);

                break;    // Idle state

            case INVALID:
                db(commProt.cRS485State);

                break;    // Idle state

            default:
                db(commProt.cRS485State);

                commProt.cRS485State = rxState.WAIT_START;

                break;
        }

        return commProt.cRS485State;
    }

    public boolean rs485Process(byte rs485RxChar) {
        boolean cOurPkt, cPktReady;

        cOurPkt = false;
        cPktReady = false;

        if (commProt.cRS485State == rxState.COMPLETE) {

            /* State Machine Received the packet */
            /* (Invalid) and destined for this node */
            if (rxCommPack.cNetAddr == commProt.cOurAddr) {
                commProt.isPacketForMe = true;
                cOurPkt = true;
                db(commProt.cRS485State.toString());
            } else {

                /*  */
                /* ClearLine2(); */
                /* LcdWrite("traffic");   Network traffic for other nodes */
                /* delay_ms(200); */
            }

            /* Validate packet CRC */
            commProt.cRS485State = postValidatePacket(rxCommPack);

            if ((commProt.cRS485State == rxState.INVALID) || (commProt.cRS485State == rxState.VALID)) {

                // Reject invalid packets
                if (commProt.cRS485State == rxState.INVALID) // NAK our invalid packets
                {

                    // ClearLine2();
                    db("Invalid pck");

                    if (commProt.cError == errState.BAD_CRC) {

                        /*    FIXME          LcdWrite("Bad CRC"); */
                    } else if (commProt.cError == errState.BAD_LENGTH) {

                        /* FIXME    what are we going to do if something is wrong */

                        // Nop(); //LcdWrite("Bad length");
                    }

                    // Delay_Us(2000);
                    // FIXME CHECK
                    // if ( cOurPkt ) Rs485SendPacket( SENSOR_NAK, 0, NULL );
                    commProt.cRS485State = rxState.WAIT_START;
                } else if (commProt.cRS485State == rxState.VALID) // If packet valid
                {    // and destined for this node
                    if (cOurPkt) {
                        db("Our Packet Valid");
                        cPktReady = true;
                        commProt.isPacketReady = true;
                    } else {
                        db("Packet Valid NOT FOR US");
                        //commProt.cRS485State = rxState.WAIT_START;
                        commProt.cRS485State = rxState.COMPLETE;
                    }
                }
            }
        }

        return cPktReady;
    }

    private rxState postValidatePacket(RxCommPack rxpack) {
        return rxState.VALID;
    }

    private RxCommPack rs485GetPacket() {
        int c;

        // byte command;
        command = rxCommPack.cCommand;
        cLen = rxCommPack.cLenExpected;

        for (c = 0; c < rxCommPack.cLenExpected; c++) {
            cData[c] = rxCommPack.c485Buf[c];
        }

        cData[rxCommPack.cLenExpected] = 0x00;    // Termninate

        return rxCommPack;
    }

    /*
     * @Override
     * public int available() throws IOException {
     * return 0;
     * }
     */
    private void db(String a) {
        System.out.println(a);
    }

    private void db(rxState rxs) {
        System.out.println(rxs.toString());
    }

    private void db(byte rs485RxChar) {
        System.out.println(rs485RxChar);

    }

    private rxState nextRxState(rxState b) {
        int a, i;

        // stateIterator = rxState.COMPLETE;
        a = b.ordinal();
        a++;

        if (a < b.values().length) {
            b = b.values()[a];
        } else {
            a = 0;
            b = b.values()[a];
        }

        return b;
    }

    /**
     * Class description
     *
     *
     * @version    Enter version here..., 09/01/20
     * @author     Enter your name here...
     */
    class CommProt {

        /** Field description */
        byte cBufPtr;    /* Pointer in Buffer */

        /** Field description */
        char cCalcCrc;    /* Our Calculated  Crc byte */

        /** Field description */
        errState cError;    /* Packet  Error */

        /** Field description */
        char cOurAddr;    /* Our address */

        /** Field description */
        rxState cRS485State;    /* state of receiveing */

        boolean isPacketForMe;

        boolean isPacketReady;
        /**
         * Constructs ...
         *
         */
        public CommProt() {
            this.cRS485State = rxState.WAIT_START;
            this.isPacketForMe = false;
            this.isPacketReady = false;
        }
    }

    /**
     * Class description
     *
     *
     * @version    Enter version here..., 09/01/20
     * @author     Enter your name here...
     */
    public class RxCommPack {

        /** Field description */
        public byte[] c485Buf;    /* Data Buffer of cLenExpected length */

        /** Field description */
        byte cCommand;    /* Received Command */

        /** Field description */
        byte cLenExpected;    /* Expected length of message */

        /** Field description */
        char cNetAddr;
        /** Field description */
        byte cRxCrc;    /* Actual received CRC byte */

        /** Field description */
        byte cStart;    /* Start char of message */

    }

    /**
     * Class description
     *
     *
     * @version    Enter version here..., 09/01/20
     * @author     Enter your name here...
     */
    private class TxCommPack {

        /** Field description */
        byte[] c485Buf;
        /** Field description */
        char cCalcCrc;
        /** Field description */
        byte cCommand;
        /** Field description */
        byte cLenExpected;
        /** Field description */
        char cNetAddr;
        /** Field description */
        byte cStart;
    }

    //FIXME?????
    public boolean isPacketComplete() {
        if (commProt.cRS485State == rxState.COMPLETE) {
            return true;
        } else {
            return false;
        }
    }
    public boolean isPacketForMe() {
        if (commProt.isPacketForMe) {
            return true;
        } else {
            return false;
        }
    }

        public boolean isPacketReady() {
        if (commProt.isPacketReady) {
            return true;
        } else {
            return false;
        }
    }


    public boolean isErrState() {
        if (commProt.cError != errState.OK_CRC) {
            return true;
        } else {
            return false;
        }
    }
}
